package workflow

import (
	"context"
	"errors"
	"fmt"
	"os"
	"os/exec"
	"sort"

	"gitee.com/okatu-loli/oc_contributor_fanyuxiao/pkg/analysis/cgo"
	"gitee.com/okatu-loli/oc_contributor_fanyuxiao/pkg/analysis/dynamic"
	"gitee.com/okatu-loli/oc_contributor_fanyuxiao/pkg/analysis/static"
	"gitee.com/okatu-loli/oc_contributor_fanyuxiao/pkg/coverage"
)

// Options controls the behaviour of the analysis workflow.
type Options struct {
	// ProjectDir is the directory containing the Go project to analyze.
	// If empty, the current working directory is used.
	ProjectDir string
	// ExecCmd is the command executed for dynamic analysis. When empty,
	// it defaults to `go test ./...`.
	ExecCmd []string
}

// Run performs static, CGO, dynamic analysis and coverage calculation.
// It returns the final list of dependency files discovered.
func Run(ctx context.Context, opts Options) ([]string, error) {
	dir := opts.ProjectDir
	if dir == "" {
		dir = "."
	}

	// ensure we run all tools from project directory
	cwd, err := os.Getwd()
	if err != nil {
		return nil, err
	}
	if err := os.Chdir(dir); err != nil {
		return nil, err
	}
	defer os.Chdir(cwd)

	var files []string
	var errs []error

	if s, err := static.AnalyzeStatic("."); err == nil {
		files = append(files, s...)
	} else {
		errs = append(errs, fmt.Errorf("static analysis: %w", err))
	}

	if c, err := cgo.AnalyzeCgo("."); err == nil {
		files = append(files, c...)
	} else {
		errs = append(errs, fmt.Errorf("cgo analysis: %w", err))
	}

	execCmd := opts.ExecCmd
	if len(execCmd) == 0 {
		execCmd = []string{"go", "test", "./..."}
	}

	if _, err := exec.LookPath("strace"); err == nil {
		if d, err := dynamic.TraceRuntimeDeps(execCmd); err == nil {
			files = append(files, d...)
			if err := coverage.Generate("runtime_deps.json"); err != nil {
				errs = append(errs, fmt.Errorf("coverage: %w", err))
			}
		} else {
			errs = append(errs, fmt.Errorf("dynamic analysis: %w", err))
		}
	} else {
		errs = append(errs, fmt.Errorf("strace not found"))
	}

	// deduplicate and sort
	if len(files) > 0 {
		m := make(map[string]struct{}, len(files))
		uniq := files[:0]
		for _, f := range files {
			if _, ok := m[f]; ok {
				continue
			}
			m[f] = struct{}{}
			uniq = append(uniq, f)
		}
		sort.Strings(uniq)
		files = uniq
	}

	return files, errors.Join(errs...)
}
